/*
 * $Id: lsrv.c,v 1.4.4.1 2005/03/21 21:39:54 aaron Exp $
 */

#include "config.h"

#include <stdio.h>
#ifdef OSF
#include <sys/bitypes.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>

#include "compat.h"

#define LSRV_BUF_SIZE 1048576*8

int main_loop(int *srv_sockets, int num_sockets);
int handle_file_client(int sock);
int handle_test_client(int sock);

int file_fd = -1;
int debug = 0;
int send_size = 0;
char buf[LSRV_BUF_SIZE];
uint64_t testsize = 0;
char *filename = NULL;
int bufsize = 0;

int main(int argc, char *argv[]) {
	extern char *optarg;
	extern int errno;
	int c;
	int port = 5002;
	static char usage[] = "usage: lsrv [-V] [-p port] [-t test size] [-f filename] [-d debug_level] [-b window size] [-s amount of file to send]\n";
	int *sockets;
	int num_sockets;
	int i;

	signal(SIGPIPE, SIG_IGN);

	while((c = getopt(argc, argv, "b:d:s:Vt:p:f:")) != -1) {
		switch(c) {
			case 'V':
				printf("Logistical Session File Server, version 0.2.\n");
				exit(1);
				break;

			case 'b':
				bufsize = atoi(optarg);
				break;

			case 'd':
				debug = atoi(optarg);
				if (debug < 1) debug = 1;
				break;

			case 's':
				send_size = atoi(optarg);
				break;

			case 't':
				testsize = atoll(optarg);
				break;

			case 'f':
				filename = strdup(optarg);
				break;

			case 'p':
				port = atoi(optarg);
				break;

			default:
				fprintf(stderr, usage); 
				exit(1);
		}
	}

	if (port <= 0 || port >= 65536) {
		printf("Error: invalid port number: %d\n", port);
		exit(-11);
	}

	if (testsize < 0) {
		printf("Error: invalid test size: %lld\n", (long long int) testsize);
		exit(-12);
	}

	if (testsize == 0 && filename == NULL) {
		printf("Error: need to send something. Specify the -f or -t flag\n");
		exit(-13);
	}

	sockets = listen_port(IPPROTO_TCP, PF_UNSPEC, port, &num_sockets, NULL);
	if (!sockets) {
		printf("Error: couldn't bind to port %d\n", port);
		exit(-1);
	}

	if (debug > 0 && bufsize > 0) {
		for(i = 0; i < num_sockets; i++) {

			int setbufsize = 0;
			SOCKLEN_T size = sizeof(int);

			if((getsockopt(sockets[i], SOL_SOCKET, SO_RCVBUF, (char *)&setbufsize, &size)) < 0) {
				perror("lsrv: getsockopt(SO_RCVBUF) failed");
			}
			else {
				printf("lsrv: SO_RCVBUF set to %d\n", setbufsize);
			}
			setbufsize = 0;
			if((getsockopt(sockets[i], SOL_SOCKET, SO_SNDBUF, (char *)&setbufsize, &size)) < 0) {
				perror("lsrv: getsockopt(SO_SNDBUF) failed");
			}
			else {
				printf("lsrv: SO_SNDBUF set to %d\n", setbufsize);
			}
		}
	}


	if (filename != NULL) {
		file_fd = open(filename, O_RDONLY, 0);
		if (file_fd < 0) {
			perror("Error: open failed");
			exit(-1);
		}
	}

	printf("Server listening on port: %d\n", port);

	main_loop(sockets, num_sockets);

	return 0;
}

int main_loop(int *srv_sockets, int num_sockets) {
	SOCKLEN_T peer_sa_len;
	fd_set sockets;
	int n, i, high;
	struct sockaddr_storage sa;
	int cfd;

	while(1) {

		FD_ZERO(&sockets);

		high = 0;

		for(i = 0; i < num_sockets; i++) {
			FD_SET(srv_sockets[i], &sockets);
			if (srv_sockets[i] > high)
				high = srv_sockets[i];
		}

		n = select(high + 1, &sockets, NULL, NULL, NULL);

		for(i = 0; i < num_sockets; i++) {
			if (FD_ISSET(srv_sockets[i], &sockets)) {

				peer_sa_len = sizeof(struct sockaddr_storage);

				cfd = accept(srv_sockets[i], (struct sockaddr *) &sa, &peer_sa_len);
				if (cfd < 0) {
					perror("main_loop: accept failed");
					continue;
				}

				if (file_fd == -1) {
					handle_test_client(cfd);
				} else {
					handle_file_client(cfd);
				}
			}
		}
	}

	return -1;
}


int handle_file_client(int sock) {
	int bytes_read;
	int bytes_written;
	uint64_t bytes_count = 0;
	int read_amt;
	uint64_t amt_remaining;
	struct timeval start_time, end_time;

	amt_remaining = testsize;

	gettimeofday(&start_time, NULL);

	lseek(file_fd, 0, SEEK_SET);

	if (testsize > 0)
		read_amt = (amt_remaining < LSRV_BUF_SIZE)?amt_remaining:LSRV_BUF_SIZE;
	else
		read_amt = LSRV_BUF_SIZE;

	bytes_read = read(file_fd, buf, read_amt);

	while (bytes_read > 0) {

		int bytes_left;

		bytes_left = bytes_read;
		bytes_written = 0;

		while(bytes_left > 0) {
			if (send_size == 0) {
				if((bytes_written = write(sock, buf + bytes_written, bytes_left)) < 0) {
					perror("write error");
					goto error_exit;
				}
			} else {

				int amount_to_send;

				amount_to_send = (send_size < LSRV_BUF_SIZE)?send_size:LSRV_BUF_SIZE;

				amount_to_send = (amount_to_send < bytes_left)?amount_to_send:bytes_left;

				if((bytes_written = write(sock, buf + bytes_written, amount_to_send)) < 0) {
					perror("write error");
					goto error_exit;
				}
			}
			bytes_left -= bytes_written;
			amt_remaining -= bytes_written;
			bytes_count += bytes_written;
		}

		if (testsize > 0)
			read_amt = (amt_remaining < LSRV_BUF_SIZE)?amt_remaining:LSRV_BUF_SIZE;
		else
			read_amt = LSRV_BUF_SIZE;

		bytes_read = read(file_fd, buf, read_amt);
	}

	shutdown(sock, SHUT_WR);

	recv(sock, buf, 1, 0);

	gettimeofday(&end_time, NULL);

	printf("--\n");
	printf("Connection type: file(%s)\n", filename);
	printf("Connection time: %f seconds\n", difftv(&start_time, &end_time));
	printf("Amount sent: %lld bytes\n", bytes_count);
	printf("Speed: %f Mb/s\n", (((double) bytes_count)/difftv(&start_time, &end_time))/1024/1024);
	printf("--\n\n");

	shutdown(sock, SHUT_WR);

	return 0;

error_exit:
	return -1;
}

int handle_test_client(int sock) {
	uint64_t amt_remaining;
	int bytes_written;
	uint64_t bytes_count = 0;
	struct timeval start_time, end_time;

	amt_remaining = testsize;

	gettimeofday(&start_time, NULL);

	while(amt_remaining > 0) {

		int amount_to_send;

		amount_to_send = (amt_remaining < LSRV_BUF_SIZE)?amt_remaining:LSRV_BUF_SIZE;

		if((bytes_written = write(sock, buf, amount_to_send)) < 0) {
			perror("write error");
			goto error_exit;
		}

		amt_remaining -= bytes_written;
		bytes_count += bytes_written;
	}

	shutdown(sock, SHUT_WR);

	recv(sock, buf, 1, 0);

	gettimeofday(&end_time, NULL);

	printf("--\n");
	printf("Connection type: test\n");
	printf("Connection time: %f seconds\n", difftv(&start_time, &end_time));
	printf("Amount sent: %lld bytes\n", bytes_count);
	printf("Speed: %f Mb/s\n", (((double) bytes_count)/difftv(&start_time, &end_time))/1024/1024);
	printf("--\n\n");

	return 0;

error_exit:
	return -1;
}
